#region Microsoft Public License
/*
 * (c) Copyright Chenjun Wu. http://utablesdk.codeplex.com
 * 
 * This source is subject to the Microsoft Public License (Ms-PL).
 * Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
 * All other rights reserved.
 */
#endregion

﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using UTable.Objects.Handlers;
using System.Windows.Media.Animation;

namespace UTable.Objects.Controls
{
	public class UGridViewPanel: Canvas
	{
		public UGridViewPanel()
		{
			
		}

		#region Properties

        public UGridView GridView { get; set; }

		#endregion

		protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved)
		{
			base.OnVisualChildrenChanged(visualAdded, visualRemoved);

			// listen to the children's event
			if (visualAdded != null && visualAdded is UGridViewItem)
			{
				SingleFingerGestureHandler inputHandler = new SingleFingerGestureHandler(visualAdded as UGridViewItem, this);
				inputHandler.GestureDetected += new SingleFingerGestureEventHandler(Child_GestureDetected);
				UGridViewItem item = visualAdded as UGridViewItem;
				item.MultiTouchProcessor.Handlers.Add(inputHandler);
                Items.Add(item);
			}
			if (visualRemoved != null && visualRemoved is UGridViewItem)
			{
				(visualRemoved as UGridViewItem).MultiTouchProcessor.Handlers.Clear();
                Items.Remove(visualRemoved as UGridViewItem);
			}

            CalculateCells();
            ArrangeChildren();
		}

		private void Child_GestureDetected(SingleFingerGestureHandler sender, SingleFingerGestureEventArgs args)
		{
			switch (args.Type)
			{
				case SingleFingerGestureType.Tap:
                    OnItemTapped(sender.Node as UGridViewItem, args as SingleFingerGestureTapEventArgs);
					break;
                case SingleFingerGestureType.DragStarted:
                    OnItemDragStarted(sender.Node as UGridViewItem, args as SingleFingerGestureDragEventArgs);
                    break;
                case SingleFingerGestureType.Dragging:
                    OnItemDragging(sender.Node as UGridViewItem, args as SingleFingerGestureDragEventArgs);
                    break;
                case SingleFingerGestureType.DragEnded:
                    OnItemDragEnded(sender.Node as UGridViewItem, args as SingleFingerGestureDragEventArgs);
                    break;
			}
		}

		private void OnItemTapped(UGridViewItem item, SingleFingerGestureTapEventArgs args)
		{
            GridView.RaiseItemTappedEvent(this, new UGridViewEventArgs(item, args.FingerPosition));
		}

		private void OnItemDragStarted(UGridViewItem item, SingleFingerGestureDragEventArgs args)
		{
            item.IsActive = true;
		}

        private void OnItemDragging(UGridViewItem item, SingleFingerGestureDragEventArgs args)
		{
            Vector offset = args.FingerPosition - args.PreviousFingerPosition;
            Canvas.SetTop(item, Canvas.GetTop(item) + offset.Y);
            Canvas.SetLeft(item, Canvas.GetLeft(item) + offset.X);
            ArrangeChildren();
		}

        private void OnItemDragEnded(UGridViewItem item, SingleFingerGestureDragEventArgs args)
		{
			if (IsItemOutofPanel(item))
			{
                GridView.RaiseItemDropOutEvent(this, new UGridViewEventArgs(item, args.FingerPosition));
				if (!GridView.DropItemOut)
				{
					// put the item at the end
					item.IsActive = false;
					this.Items.Remove(item);
					this.Items.Add(item);
					ArrangeChildren();
				}
			}
            else
            {
                item.IsActive = false;
                Cell cell = GetOccupiedCell(item);
                int newIndex = cell != null ? cell.Index : Items.Count - 1;
                int itemIndex = this.Items.IndexOf(item);
                this.Items.Remove(item);
                this.Items.Insert(newIndex, item);
                ArrangeChildren();
            }
		}

		private List<Cell> Cells = new List<Cell>();

        private List<UGridViewItem> Items = new List<UGridViewItem>();

		private class Cell
		{
			public Rect Rect { get; set; }
			public Thickness Margin { get; set; }
			public int Index { get; set; }
			public bool IsOccupied { get; set; }
		}

		private bool IsItemOutofPanel(UGridViewItem item)
		{
			Point p = new Point(Canvas.GetLeft(item), Canvas.GetTop(item));
			p = this.TranslatePoint(p, this.GridView);
			Rect item_rect = new Rect(p.X, p.Y, item.Width, item.Height);
			if (item_rect.Right <= 0 || item_rect.Bottom <= 0 || item_rect.Left > this.GridView.ActualWidth || item_rect.Top > this.GridView.ActualHeight)
				return true;
			else
				return false;
		}

		private Cell GetOccupiedCell(UGridViewItem item)
		{
			Rect item_rect = new Rect(Canvas.GetLeft(item), Canvas.GetTop(item), item.ActualWidth, item.ActualHeight);

			// the occupied cell is the cell that have the maximum intersection area
			// if the item intersects with no cell, return null
			double max_area = 0;
			Cell occupied_cell = null;
			foreach (Cell cell in Cells)
			{
				Rect intersect = Rect.Intersect(cell.Rect, item_rect);
				if (!intersect.IsEmpty)
				{
					double area = intersect.Size.Height * intersect.Width;
					if (area > max_area)
					{
						max_area = area;
						occupied_cell = cell;
					}
				}
			}
			return occupied_cell;
		}

        /// <summary>
        /// calculate the position of each cell
        /// </summary>
		internal void CalculateCells()
		{
            if (GridView == null)
                return;

            Cells.Clear();
			int cell_num = this.Children.Count;
			double panel_width = GridView.ActualWidth;
			double panel_height = GridView.ActualHeight;
            switch (GridView.LayoutDirection)
            {
                case UGridViewLayoutDirection.LeftRightDown:
                    {
						// firstly determine number of items in a line
						double available_width = panel_width - GridView.LayoutMargin.Left - GridView.LayoutMargin.Right;
						double expected_cell_width = GridView.ItemWidth + GridView.CellMargin.Left + GridView.CellMargin.Right;
						int horizontal_item_num = (int)(available_width / expected_cell_width);
                        double cell_width = available_width / horizontal_item_num;
                        double cell_height = GridView.ItemHeight + GridView.CellMargin.Top + GridView.CellMargin.Bottom;
                        double posX = GridView.LayoutMargin.Left;
                        double posY = GridView.LayoutMargin.Top;
						Thickness cell_margin = new Thickness((cell_width - GridView.ItemWidth) / 2, GridView.CellMargin.Top, (cell_width - GridView.ItemWidth) / 2, GridView.CellMargin.Bottom);
						for (int i = 0; i < cell_num; i++)
                        {
							if (i > 0 && i % horizontal_item_num == 0)
							{
								posX = GridView.LayoutMargin.Left;
								posY += cell_height;
							}
                            Cell cell = new Cell();
                            cell.Index = i;
                            cell.IsOccupied = false;
							cell.Margin = cell_margin;
							cell.Rect = new Rect(posX, posY, cell_width, cell_height);
                            posX += cell_width;
                            this.Cells.Add(cell);
                        }
						this.Width = panel_width;
						this.Height = cell_height * ((cell_num + horizontal_item_num - 1) / horizontal_item_num);
                    }
                    break;
				case UGridViewLayoutDirection.TopDownRight:
					{
						double available_height = panel_height - GridView.LayoutMargin.Top - GridView.LayoutMargin.Bottom;
						double expected_cell_height = GridView.ItemHeight + GridView.CellMargin.Top + GridView.CellMargin.Bottom;
						int vertical_item_num = (int)(available_height / expected_cell_height);
						double cell_height = available_height / vertical_item_num;
						double cell_width = GridView.ItemWidth + GridView.CellMargin.Left + GridView.CellMargin.Right;
						double posX = GridView.LayoutMargin.Left;
						double posY = GridView.LayoutMargin.Top;
						Thickness cell_margin = new Thickness(GridView.CellMargin.Left,
							(cell_height - GridView.ItemHeight) / 2, 
							GridView.CellMargin.Right,
							(cell_height - GridView.ItemHeight) / 2);
						for (int i = 0; i < cell_num; i++)
						{
							if (i > 0 && i % vertical_item_num == 0)
							{
								posY = GridView.LayoutMargin.Top;
								posX += cell_width;
							}
							Cell cell = new Cell();
							cell.Index = i;
							cell.IsOccupied = false;
							cell.Margin = cell_margin;
							cell.Rect = new Rect(posX, posY, cell_width, cell_height);
							posY += cell_height;
							this.Cells.Add(cell);
						}
						this.Height = panel_height;
						this.Width = cell_width * ((cell_num + vertical_item_num - 1) / vertical_item_num);
					}
					break;
				case UGridViewLayoutDirection.LeftRightUp:
					{
						// firstly determine number of items in a line
						double available_width = panel_width - GridView.LayoutMargin.Left - GridView.LayoutMargin.Right;
						double expected_cell_width = GridView.ItemWidth + GridView.CellMargin.Left + GridView.CellMargin.Right;
						int horizontal_item_num = (int)(available_width / expected_cell_width);
						double cell_width = available_width / horizontal_item_num;
						double cell_height = GridView.ItemHeight + GridView.CellMargin.Top + GridView.CellMargin.Bottom;
						double posX = GridView.LayoutMargin.Left;
						double posY = panel_height - GridView.LayoutMargin.Bottom - cell_height;
						Thickness cell_margin = new Thickness((cell_width - GridView.ItemWidth) / 2, GridView.CellMargin.Top, (cell_width - GridView.ItemWidth) / 2, GridView.CellMargin.Bottom);
						for (int i = 0; i < cell_num; i++)
						{
							if (i > 0 && i % horizontal_item_num == 0)
							{
								posX = GridView.LayoutMargin.Left;
								posY -= cell_height;
							}
							Cell cell = new Cell();
							cell.Index = i;
							cell.IsOccupied = false;
							cell.Margin = cell_margin;
							cell.Rect = new Rect(posX, posY, cell_width, cell_height);
							posX += cell_width;
							this.Cells.Add(cell);
						}
						this.Width = panel_width;
						this.Height = cell_height * ((cell_num + horizontal_item_num - 1) / horizontal_item_num);
					}
					break;
				case UGridViewLayoutDirection.TopDownLeft:
					{
						double available_height = panel_height - GridView.LayoutMargin.Top - GridView.LayoutMargin.Bottom;
						double expected_cell_height = GridView.ItemHeight + GridView.CellMargin.Top + GridView.CellMargin.Bottom;
						int vertical_item_num = (int)(available_height / expected_cell_height);
						double cell_height = available_height / vertical_item_num;
						double cell_width = GridView.ItemWidth + GridView.CellMargin.Left + GridView.CellMargin.Right;
						double posX = cell_width * ((cell_num + vertical_item_num - 1) / vertical_item_num) -GridView.LayoutMargin.Left - cell_width;
						double posY = GridView.LayoutMargin.Top;
						Thickness cell_margin = new Thickness(GridView.CellMargin.Left,
							(cell_height - GridView.ItemHeight) / 2,
							GridView.CellMargin.Right,
							(cell_height - GridView.ItemHeight) / 2);
						for (int i = 0; i < cell_num; i++)
						{
							if (i > 0 && i % vertical_item_num == 0)
							{
								posY = GridView.LayoutMargin.Top;
								posX -= cell_width;
							}
							Cell cell = new Cell();
							cell.Index = i;
							cell.IsOccupied = false;
							cell.Margin = cell_margin;
							cell.Rect = new Rect(posX, posY, cell_width, cell_height);
							posY += cell_height;
							this.Cells.Add(cell);
						}
						this.Height = panel_height;
						this.Width = cell_width * ((cell_num + vertical_item_num - 1) / vertical_item_num);
					}
					break;
            }
		}

		private void CheckOccupiedCell()
		{
			foreach (Cell cell in Cells)
				cell.IsOccupied = false;

			foreach (UGridViewItem item in this.Items)
			{
				if (item.IsActive)
				{
					Cell occupied_cell = GetOccupiedCell(item);
					if (occupied_cell != null)
					{
						occupied_cell.IsOccupied = true;
					}
				}
			}
		}

		private void AnimateMoveItem(UGridViewItem item, double posX, double posY)
		{
			Point fromPosition = new Point(Canvas.GetLeft(item), Canvas.GetTop(item));
			Point toPosition = new Point(posX, posY);
			double length = (toPosition - fromPosition).Length;

			TimeSpan xDuration = TimeSpan.FromMilliseconds(500);
			TimeSpan yDuration = TimeSpan.FromMilliseconds(500);

			AnimationTimeline xAnimation = AnimationUtility.CreateDoubleAnimation(posX, xDuration);
			xAnimation.FillBehavior = FillBehavior.Stop;
			xAnimation.Completed += new EventHandler(delegate(Object obj, EventArgs args)
			{
				Canvas.SetLeft(item, toPosition.X);
			});

			item.BeginAnimation(Canvas.LeftProperty, xAnimation);

			AnimationTimeline yAnimation = AnimationUtility.CreateDoubleAnimation(posY, yDuration);
			yAnimation.FillBehavior = FillBehavior.Stop;
			yAnimation.Completed += new EventHandler(delegate(Object obj, EventArgs args)
			{
				Canvas.SetTop(item, toPosition.Y);
			});

			item.BeginAnimation(Canvas.TopProperty, yAnimation);
		}

		/// <summary>
		/// Arrange all children according to their state and position
		/// </summary>
		internal void ArrangeChildren()
		{
            if (GridView == null)
                return;

			// calculate the cells occupied by the active item
            CheckOccupiedCell();

			// put the inactive items in the unoccupied cells in order
			int current_cell_index = 0;
            for (int i = 0; i < this.Items.Count; i++)
            {
                UGridViewItem item = this.Items[i];
				if (!item.IsActive)
				{
					// get the cell
					while (current_cell_index < Cells.Count)
					{
						if (!Cells[current_cell_index].IsOccupied)
							break;
						else
							current_cell_index++;
					}
					if (current_cell_index < Cells.Count)
					{
						// put the item in the cell
                        Cell cell = Cells[current_cell_index];
                        Rect item_rect = new Rect(cell.Rect.Left + cell.Margin.Left, 
                                                  cell.Rect.Top + cell.Margin.Top,
                                                  cell.Rect.Width - cell.Margin.Left - cell.Margin.Right,
                                                  cell.Rect.Height - cell.Margin.Top - cell.Margin.Bottom);

                        if (!this.IsLoaded)
                        {
                            item.Width = item_rect.Width;
                            item.Height = item_rect.Height;
                            Canvas.SetLeft(item, item_rect.Left);
                            Canvas.SetTop(item, item_rect.Top);
                        }
                        else
                        {
                            // do animation
                            item.Width = item_rect.Width;
                            item.Height = item_rect.Height;
                            AnimateMoveItem(item, item_rect.Left, item_rect.Top);
                        }
					}
					else
					{
						break;
					}
					current_cell_index++;
				}
            }
		}
	}

	public enum UGridViewLayoutDirection
	{
		LeftRightDown,
		LeftRightUp,
		TopDownRight,
		TopDownLeft,
	}
}
